//==============================================================================
//
//       DFixedPoint - the fixed point number class in the ofc-library
//
//               Copyright (C) 2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-07-02 05:51:31 $ $Revision: 1.6 $
//
//==============================================================================

#include "ofc/DFixedPoint.h"

// ToDo:
// 1. Conversion to text without the use of a double
// 2. More functions

#include <math.h>

#if _INTERFACE_

#include "ofc/config.h"

#include <limits.h>

#include "ofc/DTextable.h"
#include "ofc/DSimpleArithmetic.h"
#include "ofc/DComparable.h"

#include "ofc/DText.h"


#define DFP_MAX_POINT      ((sizeof(long) * CHAR_BIT) - 2)  // maximum value for point

//
// The fixed point class implements a 'floating point number' in a long with a 
// fixed point. The format is ..2^3+2^2+2^1+2^0+2^(-1)+2^(-2)+2^(-3)... For
// example if the point is set to 3 bits, the binary number 11011 equals to
// 3.375.
//

@interface DFixedPoint : Object <DTextable,DSimpleArithmetic,DComparable>
{
@private
  long         _value; // the value of the fixed point
  unsigned     _point; // the number of bits for the point
}

#endif



@implementation DFixedPoint


#if _PRIVATE_
//
// Read the value from an fixed point object and scale it to a point
//
// @param obj      the fixed point object
// @param point    the number of bits for the point
// 
// @return the value
//
static long getValue(DFixedPoint *obj, unsigned point)
{
  long value = obj->_value;
  
  if (obj->_point > point)
  {
    value >>= (obj->_point - point);
  }
  else if (obj->_point < point)
  {
    value <<= (point - obj->_point);
  }
  
  return value;
}
#endif


//// Constructors

//
// Initialise to fixed point to zero with no fixed point
//
// @return the object
//

- (DFixedPoint *) init
{
  [super init];

  _value = 0;
  _point = 0;

  return self;
}

//
// Initialise to a fixed point
//
// @param point    the number of bits in the point (0..DFP_MAX_POINT)
//
// @return the object
//

- (DFixedPoint *) init :(unsigned) point
{
  [self init];

  [self point :point];

  return self;
}

//
// Initialise to a fixed point
//
// @param value    the value for the fixed point
// @param point    the number of bits in the point
//
// @return the object
//

- (DFixedPoint *) init :(long) value :(unsigned) point
{
  [self init];
  
  [self set :value :point];
  
  return self;
}


//// Member methods

//
// Get the point
// 
// @return the number of bits in the point
// 
- (unsigned) point
{
  return _point;
}

//
// Change the precision of the current fixed point by changing the point
//
// @param point      the number of bits in the point (0..DFN_MAX_POINT)
//
// @return the object
//
- (DFixedPoint *) point :(unsigned) point
{
  if (point > DFP_MAX_POINT)
  {
    WARNING(DW_INVALID_ARG, "point");
  }
  else
  {
    if (point > _point)
    {
      _value <<= (point - _point);
    }
    else if (point < _point)
    {
      _value >>= (_point - point);
    }
    
    _point = point;
  }

  return self;
}

//
// Set the fixed point number (using the current point)
//
// @param value    the fixed point number
// 
// @return the object
//
- (DFixedPoint *) set :(long) value
{
  _value = value;
  
  return self;
}

//
// Set the fixed point number
// 
// @param value  the fixed point number
// @param point  the number of bits for the point
// 
// @return the object
// 
- (DFixedPoint *) set :(long) value :(unsigned) point
{
  if (point > DFP_MAX_POINT)
  {
    WARNING(DW_INVALID_ARG, "point");
  }
  else
  {
    _value = value;
    _point = point;
  }
  
  return self;
}
  
//
// Get the fixed point number
// 
// @return the fixed point number
// 
- (long) get
{
  return _value;
}

//
// Move an other object in this object
//
// @param other    the other object
//
// @return this object
//
- (DFixedPoint *) move :(DFixedPoint *) other
{
  if (other == nil) 
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    _value = other->_value;
    _point = other->_point;
  }

  return self;
}

//
// Normalize the fixed point number by removing trailing zero bits
//
// @return the object
//
- (DFixedPoint *) norm
{
  while ((_point > 0) && ((_value & 1) == 0))
  {
    _value >>= 1;
    _point--;
  }
  
  return self;
}


//// Methods on fixed point numbers

//
// Add with an other fixed point number 
//
// @param other    the fixed point number
//
// @return the object
//
- (DFixedPoint *) add :(DFixedPoint *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    _value += getValue(other, _point);
  }
  
  return self;
}

//
// Add two fixed point numbers and store the result in the object
//
// @param src1     the first fixed point number
// @param src2     the second fixed point number
//
// @return the object
//
- (DFixedPoint *) add :(DFixedPoint *) src1 :(DFixedPoint *) src2
{
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src1/src2");
  }
  else
  {
    _point = (src1->_point < src2->_point) ? src1->_point : src2->_point;
    
    _value = getValue(src1, _point) +  getValue(src2, _point);
  }
  
  return self;
}

//
// Subract with another fixed point number
//
// @param other     the fixed point number
//
// @return the object
//

- (DFixedPoint *) sub :(DFixedPoint *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    _value -= getValue(other, _point);
  }
  
  return self;
}

//
// Subract two fixed point numbers and store the result in the object
//
// @param src1      the first fixed point number
// @param src2      the second fixed point number
//
// @return the object
//
- (DFixedPoint *) sub :(DFixedPoint *) src1 :(DFixedPoint *) src2
{
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src1/src2");
  }
  else
  {
    _point = (src1->_point > src2->_point) ? src1->_point : src2->_point;
    
    _value = getValue(src1, _point) - getValue(src2, _point);
  }
  
  return self;
}

//
// Multiply with an other fixed point number
//
// @param other    the fixed point number
//
// @return the object
//

- (DFixedPoint *) mul :(DFixedPoint *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    long     value = _value * other->_value;
    unsigned point = _point + other->_point;
    
    if (point > DFP_MAX_POINT)
    {
      WARNING(DW_UNKNOWN_WARNING, "overflow on fixed point");
    }
    else
    {
      _value = value;
      _point = point;
      
      [self norm];
    }
  }

  return self;
}

//
// Multiply two fixed point numbers and store the result in the object
//
// @param src1     the first fixed point number
// @param src2     the second fixed point number
//
// @return the object
//
- (DFixedPoint *) mul :(DFixedPoint *) src1 :(DFixedPoint *) src2
{ 
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src1/src2");
  }
  else
  {
    long     value = src1->_value * src2->_value;
    unsigned point = src1->_point + src2->_point;
    
    if (point > DFP_MAX_POINT)
    {
      WARNING(DW_UNKNOWN_WARNING, "overflow on fixed point");
    }
    else
    {
      _value = value;
      _point = point;
      
      [self norm];
    }
  }
  
  return self;
}

//
// Divide with a fixed point number (Note: if the result of the division is zero
// or has to little precision, use point: to scale up the fixed point number)
//
// @param other    the divider
//
// @return the object
//
- (DFixedPoint *) div :(DFixedPoint *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else if (other->_value == 0)
  {
    WARNING(DW_UNKNOWN_WARNING, "division by zero");
  }
  else
  {
    long value = _value / other->_value;
    
    if (_point >= other->_point)
    {
      _point -= other->_point;
    }
    else
    {
      value <<= (other->_point - _point);
      
      _point = 0;
    }
    
    _value = value;
    
    [self norm];
  }
  
  return self;
}

//
// Divide two fixed point numbers and store the result in the object. (Note: 
// if the result of the division is zero or has to little precision, use point:
// to scale up the fixed point number)
//
// @param src1     the fixed point number
// @param src2     the divider
//
// @return the object
//
- (DFixedPoint *) div :(DFixedPoint *) src1 :(DFixedPoint *) src2
{
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src1/src2");
  }
  else if (src2->_value == 0)
  {
    WARNING(DW_UNKNOWN_WARNING, "division by zero");
  }
  else
  {
    long value = src1->_value / src2->_value;

    if (src1->_point >= src2->_point)
    {
      _point = src1->_point - src2->_point;
    }
    else
    {
      value <<= (src2->_point - src1->_point);
      
      _point = 0;
    }
    
    _value = value;
    
    [self norm];
  }

  return self;
}


//// Textable protocol methods

//
// Convert to a text string (decimal representation)
//
// @return a (new) text string with the fixed point number
//

- (DText *) toText
{
  DText *str      = [DText new];

  [str format :"%g", [self toDouble]]; // ToDo: algorithm without doubles
  
  return str;
}


//// Comparable protocol methods

//
// Compare two fixed point numbers
//
// @param other    the other fixed point number (!= nil)
// 
// @return the comparison result (-1 = smaller, 0 = equal, 1 = bigger)
//
- (int) compare :(DFixedPoint *) other
{
  int result = -1;
  
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    long value1 = _value;
    long value2 = other->_value;
    
    unsigned point1 = _point;
    unsigned point2 = other->_point;
    
    if ((value1 < 0) && (value2 > 0))
    {
      result = -1;
    }
    else if ((value1 > 0) && (value2 < 0))
    {
      result = 1;
    }
    else
    {
      BOOL neg = (value1 < 0);
      
      if (point1 != point2)
      {
        // Normalize the values
        while ((point1 > 0) && ((value1 & 1) == 0))
        {
          value1 >>= 1;
          point1--;
        }
        
        while ((point2 > 0) && ((value2 & 1) == 0))
        {
          value2 >>= 1;
          point2--;
        }
      }
      
      if (point1 == point2)
      {
        if (value1 == value2)
        {
          result = 0;
        }
        else if (value1 > value2)
        {
          result = 1;
        }
        else
        {
          result = -1;
        }
      }
      else
      {
        if (point1 > point2)
        {
          value1 >>= (point1 - point2);
        }
        else
        {
          value2 >>= (point2 - point1);
        }
          
        if (value1 == value2)
        {
          if (point1 > point2)
          {
            result = neg ? -1 : 1;
          }
          else // point1 < point2
          {
            result = neg ? 1 : -1;
          }
        }
        else if (value1 > value2)
        {
          result = 1;
        }
        else
        {
          result = -1;
        }
      }
    }
  }
  
  return result;
}


//// Conversion methods

//
// Convert the fixed point number to a double
// 
// @return the double
// 
- (double) toDouble
{
  return ((double) _value / (double) (1 << _point));
}

@end

/*===========================================================================*/

